/*
*  socket.c - socket input/output
*  Copyright (c) 2005 Derek Ingrouville (dergro@users.sourceforge.net)
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU Library General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#ifdef _W32_
#include <winsock.h>
#else
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <fcntl.h>
#endif /* _W32_ */

#include <ctype.h>
#include <string.h>

#include "socket.h"

Socket *
socket_new ()
{
	Socket *socket = NULL;

	socket = g_new (Socket, 1);
	socket->sock_fd = 0;
	socket->address = NULL;
	socket->port = 0;
	socket->connected = FALSE;

	return socket;
}

static int
socket_get_addr_from_ip (struct sockaddr_in *sin, const char *ip)
{
	long addr = 0;

	g_return_val_if_fail (sin != NULL, -1);
	g_return_val_if_fail (ip != NULL, -1);

#ifdef _W32_
	addr = inet_addr (ip);
	if (addr == -1)
		return -1;
	else
		sin->sin_addr.s_addr = addr;
#else
	if (!inet_aton (ip, &(sin->sin_addr)))
		return -1;
#endif

	return 0;
}

static int
socket_get_addr_from_domain (struct sockaddr_in *sin, const char *domain)
{
	struct hostent *hst = NULL;

	g_return_val_if_fail (domain != NULL, -1);
	g_return_val_if_fail (sin != NULL, -1);
	g_return_val_if_fail (strlen (domain) > 3, -1);

	if ((hst = gethostbyname (domain)) == NULL)         
		return -1;

	sin->sin_addr = *((struct in_addr *) hst->h_addr);

	return 0;
}

int
socket_connect (Socket *sock, const char *addr, int port, gboolean nonblocking)
{
	struct sockaddr_in dest_addr;
	int fd = 0;

#ifdef _W32_
	/* If we're using windows, setup winsock */
	WSADATA wsaData;
	WSAStartup (MAKEWORD (1, 1), &wsaData);
#endif /* _W32 */

	g_return_val_if_fail (addr != NULL, -1);
	g_return_val_if_fail (port > 0, -1);

	dest_addr.sin_family = AF_INET;
	dest_addr.sin_port = htons (port);

	if (isalpha (addr[0])) {
		if (socket_get_addr_from_domain (&dest_addr, addr) != 0)
			return -1;
	}
	else if (socket_get_addr_from_ip (&dest_addr, addr) != 0)
		return -1;

	/* Zero the rest of the struct */
	memset (&(dest_addr.sin_zero), 0, 8);

	fd = socket (PF_INET, SOCK_STREAM, 0);

	if (fd < 0)
		return -1;

	if (connect (fd, (struct sockaddr *) &dest_addr, sizeof (struct sockaddr)) != 0)
		return -1;

	if (nonblocking) {
#ifdef _W32_
		if (ioctlsocket (fd, FIONBIO, &nonblocking) != 0)
			return -1;
#else
		if (fcntl (fd, F_SETFL, O_NONBLOCK) == -1)
			return -1;
#endif /* _W32 */
	}

	/* Initialize members */
	sock->sock_fd = fd;
	sock->address = g_strdup (inet_ntoa (dest_addr.sin_addr));
	sock->port = port;
	sock->connected = 1;

	return 0;
}

int
socket_read (Socket *socket, char *dest_buf, unsigned int len)
{
	int ret = 0;
	int recv_len = 0;

	g_return_val_if_fail (socket != NULL, -1);
	g_return_val_if_fail (dest_buf != NULL, -1);
	g_return_val_if_fail (len > 0, -1);
	g_return_val_if_fail (socket->connected, -1);

	while (recv_len < len) {
		ret = recv (socket->sock_fd, dest_buf + recv_len, len - recv_len, 0);

		if (ret == 0) {
			/* Remote closed socket */
			socket_close (socket);
			break;
		}

		if (ret < 0)
			break;

		recv_len += ret;
	}

	if (recv_len > 0)
		return recv_len;
	else
		return -1;

	return recv_len;
}

int
socket_write (Socket *socket, char *data, int len)
{
	int ret = 0;
	int sent = 0;

	g_return_val_if_fail (socket != NULL, -1);
	g_return_val_if_fail (data != NULL, -1);
	g_return_val_if_fail (len > 0, -1);
	g_return_val_if_fail (socket->sock_fd > 0, -1);
	g_return_val_if_fail (socket->connected, -1);

	while (sent < len) {
		ret = send (socket->sock_fd, data, len, 0);
		if (ret > 0)
			sent += ret;
		else
			return -1;
	}

	return sent;
}

unsigned int
socket_data_available (Socket *socket)
{
	struct timeval tv;
	fd_set fds;

	g_return_val_if_fail (socket != NULL, 0);
	g_return_val_if_fail (socket->sock_fd > 0, 0);
	g_return_val_if_fail (socket->connected, 0);

	tv.tv_sec = 0;
	tv.tv_usec = 1 * 1000; /* Microseconds */

	FD_ZERO (&fds);
	FD_SET (socket->sock_fd, &fds);

	select (socket->sock_fd + 1, &fds, NULL, NULL, &tv);

	if (FD_ISSET (socket->sock_fd, &fds))
		return 1;
	else
		return 0;
}

void
socket_close (Socket *socket)
{
	g_return_if_fail (socket != NULL);

	if (socket->sock_fd > 0) {
#ifdef _W32_
		closesocket (socket->sock_fd);
#else
		close (socket->sock_fd);
#endif /* _W32 */
		socket->sock_fd = 0;
	}

	if (socket->address != NULL)
		g_free (socket->address);

	g_free (socket);

	return;
}
